﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Daybreak.Core.SceneGraph;
using Daybreak.Core.Rendering.Materials;
using Daybreak.Core.Rendering.Pipeline;

namespace Daybreak.Core.Rendering
{
    internal class RenderSubItem : RenderQueue.Item<RenderSubItem>
    {
        /// <summary>
        /// The index to start at in the index buffer.
        /// </summary>
        public int StartIndex;
        /// <summary>
        /// The number of vertices to use from the vertex buffer.
        /// </summary>
        public int VertexCount;
        /// <summary>
        /// The type of primitive to use when rendering this instance.
        /// </summary>
        public PrimitiveType PrimitiveType;
        /// <summary>
        /// The index buffer to render with. (could be null - DrawPrimitives will be called then)
        /// </summary>
        public IndexBuffer IndexBuffer;

        int primitiveCount;
        IMaterial material;

        public override void Reset()
        {

        }

        public override RenderSubItem Clone()
        {
            return new RenderSubItem();
        }

        public override void CacheNodeTransform(Transform node, ref Matrix matrix)
        {

        }

        public override void DrawSelf(GraphicsDevice device)
        {
            if (IndexBuffer != null)
                device.DrawIndexedPrimitives(PrimitiveType, 0, 0, VertexCount, StartIndex, PrimitiveCount);
            else
                device.DrawPrimitives(PrimitiveType, 0, PrimitiveCount);

        }

        public override void SetSquaredDistance(ref Vector3 pos)
        {
        }

        public override IMaterial Material
        {
            get
            {
                return material;
            }
            set
            {
                material = value;
            }
        }

        public override int PrimitiveCount
        {
            get { return primitiveCount; }
            set { primitiveCount = value; }
        }

    }

    /// <summary>
    /// This class incapsulates all necessary info to make one DIP call (DrawIndexedPrimitives)
    /// </summary>
    /// <remarks>
    /// A render item instance stores all the information necessary to render an object. 
    /// It is completely detached from the object itself, though. 
    /// This allows the scene renderer and specific render managers to perform various rendering optimizations 
    /// like batching and setting common render states.
    /// </remarks>
    public class RenderItem : RenderQueue.Item<RenderItem>
    {
        /// <summary>
        /// The index to start at in the index buffer.
        /// </summary>
        public int StartIndex;
        /// <summary>
        /// The visibility level of the object.
        /// </summary>
        public float Opacity;
        /// <summary>
        /// The type of primitive to use when rendering this instance.
        /// </summary>
        public PrimitiveType PrimitiveType;
        /// <summary>
        /// The vertex buffer to render with.
        /// </summary>
        public VertexData VertexData;
        /// <summary>
        /// The index buffer to render with. (could be null - DrawPrimitives will be called then)
        /// </summary>
        public IndexBuffer IndexBuffer;
        /// <summary>
        /// The bounding box in world space of the object.
        /// </summary>
        public BoundingBox WorldBoundingVolume;
        /// <summary>
        /// The absolute transform of the object.
        /// </summary>
        public Matrix WorldTransform;

        public bool Wireframe;

        int primitiveCount;
        IMaterial material;


        public RenderItem() { }

        public override void Reset()
        {
            VertexData = null;
            StartIndex = 0;
            PrimitiveCount = 0;
            Material = null;
            Opacity = 1;
            IndexBuffer = null;
            VertexData = null;
            WorldTransform = Matrix.Identity;

        }

        public override RenderItem Clone()
        {
            return new RenderItem();
        }

        public override void CacheNodeTransform(Transform node, ref Matrix matrix)
        {
            WorldTransform = matrix;
        }

        public override void DrawSelf(GraphicsDevice device)
        {
            if (IndexBuffer != null)
                device.DrawIndexedPrimitives(this.PrimitiveType, VertexData.BaseVertex, 0, VertexData.VertexCount, StartIndex, PrimitiveCount);
            else
                device.DrawPrimitives(PrimitiveType, 0, PrimitiveCount);
        }

        public override void SetupInstance(GraphicsDevice device, Pipes.ObjectContext objContext)
        {
            VertexData.ApplyToDevice(device, material.Effect.UseTangents);

            device.Indices = IndexBuffer;

            if (Wireframe)
                device.RenderState.FillMode = FillMode.WireFrame;
            else
                device.RenderState.FillMode = FillMode.Solid;

            objContext.SetWorldMatrix(ref WorldTransform);

        }

        public override void SetSquaredDistance(ref Vector3 pos)
        {
            Vector3 center;

            Transform.BBoxCenter(ref WorldBoundingVolume, out center);

            Vector3.DistanceSquared(ref center, ref pos, out SquaredDistance);

        }


        public override IMaterial Material
        {
            get { return material; }
            set { material = value; }
        }

        public override int PrimitiveCount
        {
            get { return primitiveCount; }
            set { primitiveCount = value; }
        }

    }

    public class TerrainBatch : RenderQueue.Item<TerrainBatch>
    {
        /// <summary>
        /// The number of vertices to use from the vertex buffer.
        /// </summary>
        public int VertexCount;
        /// <summary>
        /// The visibility level of the object.
        /// </summary>
        public float Opacity;
        /// <summary>
        /// The type of primitive to use when rendering this instance.
        /// </summary>
        public PrimitiveType PrimitiveType;
        /// <summary>
        /// The vertex buffer to render with.
        /// </summary>
        public VertexBuffer VertexBuffer;
        /// <summary>
        /// The index buffer to render with. (could be null - DrawPrimitives will be called then)
        /// </summary>
        public IndexBuffer IndexBuffer;
        /// <summary>
        /// The type of vertex data in the vertex buffer.
        /// </summary>
        public VertexDeclaration Declaration;
        /// <summary>
        /// The absolute transform of the object.
        /// </summary>
        public Matrix WorldTransform;

        public bool Wireframe;

        int primitiveCount;
        IMaterial material;


        public TerrainBatch() { }

        public override void Reset()
        {
            PrimitiveCount = 0;
            VertexCount = 0;
            Material = null;
            Opacity = 1;
            IndexBuffer = null;
            VertexBuffer = null;
            Declaration = null;
            WorldTransform = Matrix.Identity;

        }

        public override TerrainBatch Clone()
        {
            return new TerrainBatch();
        }

        public override void CacheNodeTransform(Transform node, ref Matrix matrix)
        {
            WorldTransform = matrix;
        }

        public override CompositeBatch Optimize(RenderQueue queue, int primitivesPerBatch)
        {
            if (PrimitiveCount <= primitivesPerBatch)
                return null;

            RenderSubItem item = null;
            CompositeBatch batch = queue.AllocateSlot<CompositeBatch>(false);

            batch.Parent = this;

            int count = PrimitiveCount / primitivesPerBatch;
            int mod = PrimitiveCount % primitivesPerBatch;

            int total = count * primitivesPerBatch + mod;
            int i = 0;

            for (i = 0; i < count; i++)
            {
                item = queue.AllocateSlot<RenderSubItem>(false);
                batch.AddItem(item);

                InitSubItem(item);

                item.StartIndex = primitivesPerBatch * i * 3;
                item.PrimitiveCount = primitivesPerBatch;

            }

            if (mod > primitivesPerBatch / 2)
            {
                item = queue.AllocateSlot<RenderSubItem>(false);
                batch.AddItem(item);

                InitSubItem(item);

                item.StartIndex = primitivesPerBatch * i * 3;
                item.PrimitiveCount = mod;
            }
            else
            {
                item.PrimitiveCount += mod;
            }


            return batch;
        }

        private void InitSubItem(RenderSubItem item)
        {
            item.IndexBuffer = IndexBuffer;
            item.Material = Material;
            item.PrimitiveType = this.PrimitiveType;
            item.VertexCount = VertexCount;
        }

        public override void DrawSelf(GraphicsDevice device)
        {
            if (PrimitiveCount == 0)
                return;

            if (IndexBuffer != null)
                device.DrawIndexedPrimitives(this.PrimitiveType, 0, 0, VertexCount, 0, PrimitiveCount);
            else
                device.DrawPrimitives(PrimitiveType, 0, PrimitiveCount);

        }

        public override void SetupInstance(GraphicsDevice device, Pipes.ObjectContext objContext)
        {
            device.VertexDeclaration = Declaration;
            device.Indices = IndexBuffer;
            device.Vertices[0].SetSource(VertexBuffer, 0, Declaration.GetVertexStrideSize(0));

            if (Wireframe)
                device.RenderState.FillMode = FillMode.WireFrame;
            else
                device.RenderState.FillMode = FillMode.Solid;

            objContext.SetWorldMatrix(ref WorldTransform);


        }

        public override void SetSquaredDistance(ref Vector3 pos)
        {
            Vector3 center = WorldTransform.Translation;

            Vector3.DistanceSquared(ref center, ref pos, out SquaredDistance);

        }

        public override IMaterial Material
        {
            get { return material; }
            set { material = value; }
        }

        public override int PrimitiveCount
        {
            get { return primitiveCount; }
            set { primitiveCount = value; }
        }

    }

    public class CompositeBatch : RenderQueue.Item<CompositeBatch>
    {
        public RenderQueue.Item Parent;
        ResetableList<RenderQueue.Item> _childItems = new ResetableList<RenderQueue.Item>(16);

        public void AddItem(RenderQueue.Item item)
        {
            _childItems.Add(item);
        }

        public override void Reset()
        {
            _childItems.Reset(false);
            Parent = null;
        }

        public override CompositeBatch Clone()
        {
            return new CompositeBatch();
        }

        public override void CacheNodeTransform(Transform node, ref Matrix matrix)
        {
            Parent.CacheNodeTransform(node, ref matrix);
        }

        public override void BeforeRender(GraphicsDevice device)
        {
            Parent.BeforeRender(device);
        }
        public override void DrawSelf(GraphicsDevice device)
        {
            for (int i = 0; i < _childItems.ItemsCount; i++)
                _childItems.GetAt(i).DrawSelf(device);
        }
        public override void AfterRender(GraphicsDevice device)
        {
            Parent.AfterRender(device);
        }

        public override void SetupInstance(GraphicsDevice device, Pipes.ObjectContext objContext)
        {
            Parent.SetupInstance(device, objContext);
        }

        public override void SetSquaredDistance(ref Vector3 pos)
        {
            Parent.SetSquaredDistance(ref pos);
        }

        public int ItemsCount
        {
            get { return _childItems.ItemsCount; }
        }

        public override IMaterial Material
        {
            get
            {
                return Parent.Material;
            }
            set
            {
                throw new NotImplementedException();
            }
        }


        public override int PrimitiveCount
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }
    }

    public class BBoxItem : RenderQueue.Item<BBoxItem>
    {
        private static Matrix IdentityMatrix = Matrix.Identity;
        private static Vector3[] bboxCorners = new Vector3[8];
        private static VertexDeclaration decl;
        private static short[] wfBoxIndices;
        private static VertexPositionColor[] wfBoxVertices = new VertexPositionColor[8];

        /// <summary>
        /// The bounding box in local space of the object.
        /// </summary>
        public BoundingBox BoundingVolume;

        IMaterial material;

        //public Color BoxColor = Color.Red;

        static BBoxItem()
        {
            wfBoxIndices = new short[] 
            { 
                0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7
            };

            for (int i = 0; i < wfBoxVertices.Length; i++)
            {
                wfBoxVertices[i].Color = Color.White;
            }
        }

        public BBoxItem()
        {
        }

        public override void Reset()
        {
            Material = null;
        }

        public override BBoxItem Clone()
        {
            return new BBoxItem();
        }

        public override void CacheNodeTransform(Transform node, ref Matrix matrix)
        {
        }

        public override void DrawSelf(GraphicsDevice device)
        {
            device.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.LineList, wfBoxVertices, 0, 8, wfBoxIndices, 0, PrimitiveCount);
        }

        public override void SetupInstance(GraphicsDevice device, Pipes.ObjectContext objContext)
        {
            BoundingVolume.GetCorners(bboxCorners);

            for (int i = 0; i < bboxCorners.Length; i++)
                wfBoxVertices[i].Position = bboxCorners[i];

            device.VertexDeclaration = Declaration;

            objContext.SetWorldMatrix(ref IdentityMatrix);

        }

        public override void SetSquaredDistance(ref Vector3 pos)
        {

        }

        public override IMaterial Material
        {
            get { return material; }
            set { material = value; }
        }

        public override int PrimitiveCount
        {
            get { return 12; }
            set { }
        }


        private VertexDeclaration Declaration
        {
            get
            {
                if (decl == null)
                    decl = Engine.CreateDeclaration(VertexPositionColor.VertexElements);

                return decl;
            }
        }

    }
}
